package com.kamistoat.ruoyi.ruoyigateway.filter;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.kamistoat.ruoyi.ruoyicommoncore.constant.CacheConstants;
import com.kamistoat.ruoyi.ruoyicommoncore.constant.Constants;
import com.kamistoat.ruoyi.ruoyicommoncore.domain.R;
import com.kamistoat.ruoyi.ruoyicommoncore.exception.CaptchaException;
import com.kamistoat.ruoyi.ruoyicommoncore.exception.user.CaptchaExpireException;
import com.kamistoat.ruoyi.ruoyicommoncore.utils.ServletUtils;
import com.kamistoat.ruoyi.ruoyicommoncore.utils.StringUtils;
import com.kamistoat.ruoyi.ruoyicommonredis.utils.RedissonUtils;
import com.kamistoat.ruoyi.ruoyigateway.config.properties.CaptchaProperties;
import com.kamistoat.ruoyi.ruoyigateway.service.ValidateCodeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicReference;

/**
 * 自定义过滤器，用于对验证码进行验证。
 * 这个过滤器将对转发给 ruoyi-auth 模块的请求进行前置过滤。
 * 在yaml中可以看到，所有目标为 ruoyi-auth 的请求都会先进这个过滤器然后才会转发。
 */
@Component
public class ValidateCodeFilter extends AbstractGatewayFilterFactory<Object> {

    @Autowired
    ValidateCodeService validateCodeService;
    @Autowired
    CaptchaProperties captchaProperties;
    @Autowired
    RedissonUtils redissonUtils;

    // 定义需要验证码的路径。例如在这里就是登录时/注册时需要提交验证码。如果有其他路径在这里加上就可以
    // 另外注意，如果去看 ruoyi-auth 模块的服务接口，可以看到登录/注册的接口其实是 /login 和 /register
    // 这里正是因为该过滤器是在请求转发之前的前置过滤，此时接收的路径是前端发来的 /auth/login 和 /auth/register, 还没有裁剪路径进行转发呢
    private static final String[] VALIDATE_URL = new String[]{"/auth/login", "/auth/register"};
    // 前端body中输入的验证码所使用的key
    private static final String CODE = "code";
    // 前端body中携带的缓存uuid使用的key
    private static final String UUID = "uuid";

    @Override
    public GatewayFilter apply(Object config) {

        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                ServerHttpRequest request = exchange.getRequest();

                // 如果不是需要验证码的请求路径则不进行处理
                if (!StringUtils.containsAnyIgnoreCase(request.getURI().getPath(), VALIDATE_URL) || !captchaProperties.getEnabled()) {
                    return chain.filter(exchange);
                }

                // 校验验证码
                try {
                    // 截取body并以String类型返回
                    String bodyFromRequest = resolveBodyFromRequest(request);
                    // 将String转换为JSON，从而提取k-v对
                    JSONObject jsonObject = JSON.parseObject(bodyFromRequest);
                    // 校验验证码
                    validateCodeService.checkCaptcha(jsonObject.getString(CODE), jsonObject.getString(UUID));
                } catch (Exception exception) {
                    // 接口只会抛出 CaptchaException异常，保险起见捕获所有异常
                    if(exception instanceof CaptchaException){
                        // 如果是验证码验证不通过，则请求本身是200响应码，但是请求处理是70000错误码
                        return ServletUtils.webFluxResponseWriter(exchange.getResponse(),
                                HttpStatus.OK,
                                exception.getMessage(),
                                Constants.CAPTCHA_ERROR_CODE);
                    }else{
                        // 其他异常就是未知异常
                        return ServletUtils.webFluxResponseWriter(exchange.getResponse(),
                                HttpStatus.OK,
                                "内部未知异常，请联系管理员",
                                Constants.GATEWAY_UNKNOWN_ERROR_CODE);
                    }
                }

                // 未cache到异常说明验证码校验成功，则直接继续过滤器链。
                return chain.filter(exchange);
            }
        };
    }

    /**
     * TODO 不懂。ServerHttpRequest可接收多个Body，此方法将返回最后一个Body的String形式
     *
     * @param serverHttpRequest
     * @return
     */
    private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest) {
        // 响应式编程的两个主要流 Flux和Mono。前者内部可以有多个元素，后者内部最多1个元素。
        // 从响应式Request中提取出body，以Flux流形式，流内的元素为内存缓冲。ServerHttpRequest可以接收多body。同样是响应式编程的内容
        Flux<DataBuffer> body = serverHttpRequest.getBody();
        // AtomicReference 保证 对象引用的异步原子性写入。
        // 不过不是很懂有必要在这里对压根不会有多线程操作的 ServerHttpRequest用原子类嘛？况且下面用的 set 方法也不是 CAS原子方法
        AtomicReference<String> bodyRef = new AtomicReference<>();
        body.subscribe(dataBuffer -> {
            // 遍历Flux流
            // 首先将 DataBuffer 解码为 CharBuffer
            CharBuffer charBuffer = StandardCharsets.UTF_8.decode(dataBuffer.asByteBuffer());
            // 释放掉 dataBuffer所占的内存缓冲
            DataBufferUtils.release(dataBuffer);
            // 存疑，这里每次set是set新值，都会覆盖旧值。那这里的遍历的意义就是Flux中的最后一个dataBuffer的值才会被设置到bodyRef中?
            bodyRef.set(charBuffer.toString());
        });

        // 将返回 Flux中的最后一个dataBuffer的的charBuffer？
        return bodyRef.get();
    }
}
